package top.as.sean.common.guava;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import annotation.DictField;
import top.as.sean.common.context.SpringContextUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * guava本地缓存
 *
 * @author: ArchieSean
 * @create: 2022/11/6
 */
@Slf4j
public class GuavaCache {
    /**
     * 需要翻译的属性注解标记缓存
     */
    private final Map<String, DictField> fieldMap;
    /**
     * guava装饰后的线程池
     */
    private final ListeningExecutorService backgroundRefreshPools;
    /**
     * guava本地缓存数据
     */
    private final LoadingCache<String, Map<String, String>> caches;

    public GuavaCache(GuavaProperties guavaProperties) {
        //初始化线程池
        this.backgroundRefreshPools = MoreExecutors.listeningDecorator(
                new ThreadPoolExecutor(guavaProperties.getCorePoolSize(),
                        guavaProperties.getMaximumPoolSize(),
                        guavaProperties.getKeepAliveTime(),
                        TimeUnit.SECONDS,
                        guavaProperties.getWorkQueue(),
                        guavaProperties.getThreadFactory(),
                        guavaProperties.getHandler()
                )
        );
        //初始化缓存标记
        this.fieldMap = new HashMap<>();
        //初始化缓存
        this.caches = CacheBuilder.newBuilder()
                .maximumSize(guavaProperties.getNumMaxSize())
                .refreshAfterWrite(guavaProperties.getRefreshWriteTime(), TimeUnit.MINUTES)
                .build(new CacheLoader<String, Map<String, String>>() {
                    @NotNull
                    @Override
                    public Map<String, String> load(@NotNull String key) throws Exception {
                        log.debug("首次读取缓存，key：" + key);
                        DictField dictField = fieldMap.get(key);
                        Object bean = SpringContextUtil.getBean(dictField.clazz());
                        Method method = dictField.clazz().getMethod(dictField.method(), String.class);
                        return (Map<String, String>) method.invoke(bean, dictField.key());
                    }

                    @NotNull
                    @Override
                    public ListenableFuture<Map<String, String>> reload(@NotNull String key,
                                                                        @NotNull Map<String, String> oldValue) {
                        return backgroundRefreshPools.submit(() -> {
                            log.debug("异步刷新缓存: " + key);
                            DictField dictField = fieldMap.get(key);
                            Object bean = SpringContextUtil.getBean(dictField.clazz());
                            Method method = dictField.clazz().getMethod(dictField.method(), String.class);
                            return (Map<String, String>) method.invoke(bean, dictField.key());
                        });
                    }
                });
    }

    /**
     * 集合翻译
     *
     * @param list list
     */
    public void translateList(List<?> list) throws Exception {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        Class<?> clazz = list.get(BigDecimal.ZERO.intValue()).getClass();

        //获取需要翻译的属性
        List<@Nullable Field> fieldList = Lists.newArrayList();
        //缓存中没有的数据(key:属性，值：对应的字典数据)
        Map<String, Map<String, String>> invokes = new HashMap<>(20);

        getFieldListAndData(clazz, fieldList, invokes);
        if (CollectionUtils.isEmpty(fieldList)) {
            return;
        }
        //数据封装
        list.forEach(item -> mergeObjFieldValue(item, fieldList, invokes));

    }

    /**
     * 翻译单个对象
     *
     * @param object 需要翻译的对象
     * @throws Exception ex
     */
    public void translateOne(Object object) throws Exception {
        if (ObjectUtils.isEmpty(object)) {
            return;
        }
        Class<?> clazz = object.getClass();
        //获取需要翻译的属性
        List<@Nullable Field> fieldList = Lists.newArrayList();
        //缓存中有的数据(key:属性，值：对应的字典数据)
        Map<String, Map<String, String>> invokes = new HashMap<>(20);

        getFieldListAndData(clazz, fieldList, invokes);
        if (CollectionUtils.isEmpty(fieldList)) {
            return;
        }
        mergeObjFieldValue(object, fieldList, invokes);
    }

    /**
     * 反射获取需要翻译的字段，获取缓存数据
     *
     * @param clazz     字节码
     * @param fieldList 需要翻译的属性
     * @param invokes   缓存数据
     * @throws Exception ex
     */
    private void getFieldListAndData(Class<?> clazz,
                                     List<@Nullable Field> fieldList,
                                     Map<String, Map<String, String>> invokes)
            throws Exception {
        Field[] fields = clazz.getDeclaredFields();
        String clazzName = clazz.getName();
        for (Field field : fields) {
            field.setAccessible(true);
            DictField dictField = field.getAnnotation(DictField.class);
            if (Optional.ofNullable(dictField).isPresent()) {
                fieldList.add(field);
                String fieldName = field.getName();
                String key = clazzName + fieldName;
                fieldMap.put(key, dictField);
                //从本地缓存获取
                //缓存没有时，查询一次
                Map<String, String> map = caches.get(key);
                if (map.size() > 0) {
                    invokes.put(fieldName, map);
                    continue;
                }
                Method method = dictField.clazz().getMethod(dictField.method(), String.class);
                Object bean = SpringContextUtil.getBean(dictField.clazz());
                Map<String, String> value = (Map<String, String>) method.invoke(
                        bean, dictField.key());
                invokes.put(field.getName(), value);
            }
        }
    }

    /**
     * 合并属性翻译
     *
     * @param obj       需要翻译的对象
     * @param fieldList 翻译的字段
     * @param invokes   对应的字典
     */
    private void mergeObjFieldValue(Object obj, List<Field> fieldList,
                                    Map<String, Map<String, String>> invokes) {
        fieldList.forEach(field -> {
            field.setAccessible(true);
            try {
                //获取到属性值
                Object o = field.get(obj);
                //获取属性名称
                String fieldName = field.getName();
                if (Optional.ofNullable(o).isPresent()
                        && invokes.get(fieldName).containsKey(o.toString())) {

                    field.set(obj, invokes.get(fieldName).get(o.toString()));
                }
            } catch (Exception e) {
                log.info("数据属性合并异常:{},{}", field, e);
                throw new RuntimeException("数据处理失败：" + field, e);
            }
        });
    }
}
